// -*- c -*-

#define LCD_WR   A1
#define LCD_RS   A2
#define LCD_CS   A3
#define LCD_RST  A4

static uint8_t maskWR, maskRS;
static volatile uint8_t *portregRS, *portregWR;

// ----------------------------------------------------------------------
//  PORTD = (PORTD & B00000011) | ((uint8_t)d & B11111100);
//  PORTB = (PORTB & B11111100) | ((uint8_t)d & B00000011);

#define Lcd_Write_Bus(d) \
  PORTD = ((uint8_t)d & B11111100); \
  PORTB = ((uint8_t)d & B00000011); \
  *(portregWR) &= ~maskWR; *(portregWR) |= maskWR;

static uint8_t mode = 1;

void Lcd_Write_Com (uint8_t b)
{
  if (mode) {
    *(portregRS) &= ~maskRS;
    mode = 0;
  }
  Lcd_Write_Bus (b);
}

void Lcd_Write_Data (uint8_t b)
{
  if (!mode) {
    *(portregRS) |= maskRS;
    mode = 1;
  }
  Lcd_Write_Bus (b);
}

void Lcd_Write_Data16 (uint16_t d)
{
  if (!mode) {
    *(portregRS) |= maskRS;
    mode = 1;
  }
  Lcd_Write_Bus ((d >> 8));
  Lcd_Write_Bus ((d & 255));
}

#define HWSCROLL 0
#define SCROLL(x) if (HWSCROLL == 1) { x; }

void Lcd_Init(void)
{
  maskWR = digitalPinToBitMask (LCD_WR);
  maskRS = digitalPinToBitMask (LCD_RS);
  portregRS = portOutputRegister (digitalPinToPort(LCD_RS));
  portregWR = portOutputRegister (digitalPinToPort(LCD_WR));

  digitalWrite (LCD_RST, HIGH); delay(5);
  digitalWrite (LCD_RST, LOW);  delay(15);
  digitalWrite (LCD_RST, HIGH); delay(15);

  digitalWrite (LCD_CS, HIGH);
  digitalWrite (LCD_WR, HIGH);
  digitalWrite (LCD_CS, LOW);

  Lcd_Write_Com (0xC0); // Power control 1
  Lcd_Write_Data (0x23); // VRH[5:0] 23h = 3.80V

  Lcd_Write_Com (0xC1); // Power control  2
  Lcd_Write_Data (0x10); // SAP[2:0], BT[3:0]

  Lcd_Write_Com(0xC5); // VCM control
  Lcd_Write_Data(0x31); // Contrast VMH[6:0]  (default)
  Lcd_Write_Data(0x3c); // VML[6:0] (default)

  Lcd_Write_Com(0xC7); // VCM control2
  Lcd_Write_Data(0xC0); // VMF[6:0]

  Lcd_Write_Com(0x36); // Memory Access Control
  Lcd_Write_Data(0x48); // MY,MX,MV,ML,BGR,MH,x,x  MY=0 MX=0 MV=0 ML=0 BGR=1 MH=0 00

  Lcd_Write_Com(0x3A); // Pixel Format Set
  Lcd_Write_Data(0x55); // x, DPI[2:0], x, DBI[2:0] 55h = 16 bit mode DPI=101 DBI=101

  Lcd_Write_Com(0xF6); // Interface Control
  Lcd_Write_Data(0x01); // MY_EOR,MX_EOR,MV_EOR,0,BGR_EOR,0,0,WE_MODE=1=wraparound
  Lcd_Write_Data(0x30); // 0,0,EPF[1:0],0,0,MDT{1:0] EPF=11 makes White look better
  Lcd_Write_Data(0x00); /* 0,0,ENDIAN,0,DM[1:0],RM,RIM */

  Lcd_Write_Com(0xB1); // Frame Control (in normal mode)
  Lcd_Write_Data(0x00); // DIVC[1:0]
  Lcd_Write_Data(0x1b); // RTNB[4:0]

  Lcd_Write_Com(0xB6); // Display Function Control
  Lcd_Write_Data(0x08); // PTG[1:0], PT[1:0]
  Lcd_Write_Data(0x82); // REV,GS,SS,SM,ISC[3:0]
  Lcd_Write_Data(0x27); // NL[5:0]   100111 = 320 lines

  Lcd_Write_Com(0x51); // Display Brightness
  Lcd_Write_Data(0xFF);

  SCROLL(Lcd_Write_Com (0x33); // Vertical Scrolling Definition
	 Lcd_Write_Data16 (0);
	 Lcd_Write_Data16 (320);
	 Lcd_Write_Data16 (0));

  Lcd_Write_Com(0x11); // Sleep out
  delay(120);

  Lcd_Write_Com(0x29); // Display on
}


void Address_set (uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
  Lcd_Write_Com (0x2a);		/* Column/Column Address Set */
  Lcd_Write_Data16 (x1);
  Lcd_Write_Data16 (x2);

  Lcd_Write_Com (0x2b);		/* Page/Row Address Set */
  Lcd_Write_Data16 (y1);
  Lcd_Write_Data16 (y2);

  Lcd_Write_Com (0x2c);
}


void Rect(int x, int y, int w, int h, uint16_t c)
{
  if ((x >= 0) && ((x+w) <= 240) && (y >= 0) && ((y+h <= 320)))
    {
      Address_set (x, y, x+w-1, y+h-1);

      for (int yy = h; yy > 0; yy--)
	for (int xx = w; xx > 0; xx--)
	  Lcd_Write_Data16 (c);
    }
}

// ----------------------------------------------------------------------

char* wifiinit[] =
  {
    "\r\nAT\r\n",
    "AT+CWMODE=1\r\n",
    "AT+CWJAP=\"VK2CJB/P\",\"postieeasyrider\"\r\n",
    "AT+CIPMUX=1\n\r",
    "AT+CIPSERVER=1,6666\r\n",
    0
  };

void setup (void)
{
  // LCD

  for (int p = 0; p < 10; p++) pinMode(p,OUTPUT);
  pinMode (LCD_WR, OUTPUT);
  pinMode (LCD_RS, OUTPUT);
  pinMode (LCD_CS, OUTPUT);
  pinMode (LCD_RST ,OUTPUT);
  digitalWrite (LCD_WR, HIGH);
  digitalWrite (LCD_RS, HIGH);
  digitalWrite (LCD_CS, HIGH);
  digitalWrite (LCD_RST, HIGH);
  Lcd_Init();
  Rect (0,0, 240,320, 0xF000);

  // Wifi/ESP8266
  char buffer[20];

  Serial.begin (9600);
  while (!Serial) continue;
  Serial.setTimeout(9999);
  int to = 0, pa = 0;

  for (char** p = wifiinit; *p != 0;)
    {
      Serial.print (*p);
      while (1)
	{
	  int to = Serial.readBytesUntil(10, buffer, 19);
	  delay(100);
	  if (to)
	    {
	      if (strncmp(buffer, "OK", 2) == 0 ||
		  strncmp(buffer, "no change", 9) == 0)
		{
		  p++;
		  break;
		}
	    }
	}
    }
  Rect (0,0, 240,320, 0x3F7F);

  Serial.print ("AT+BAUD=115200\r\n");
  Serial.flush ();
  Serial.end ();
  Serial.begin (115200);

  Rect (0,0, 240,320, 0x1F1F);
}


int gethex (void)
{
  while (Serial.available() < 1) continue;
  int t = Serial.read() - 48;
  if (t > 9) t -= 7;
  return ((t >= 0) && (t <= 15)) ? t : -1;
}

void loop()
{
  while (Serial.read() != '*') continue;

  uint16_t colour = gethex();
  colour = (colour << 4) | gethex();
  colour = (colour << 4) | gethex();
  colour = (colour << 4) | gethex();

  while (1)
    {
      while (Serial.available() < 1) continue;
      if (Serial.peek() < 32) break;

      uint64_t t = 0;
      int n;
      while ((n = gethex()) != -1)
	t = (t << 4) | n;

      uint32_t len = t / 76800; len++;
      uint32_t pos = t % 76800;
      int posy = pos / 240;
      int posx = pos % 240;

      if (len > 76801) break;
      if (posy > 319) break;
      if (posx > 239) break;
      
      Address_set (posx, posy, 239, posy);

      while (len--)
	{
	  Lcd_Write_Data16 (colour);
	  if (++posx == 240)
	    {
	      posx = 0;
	      if (++posy == 320) break;
	      Address_set (posx, posy, 239, posy);
	    }
	}
    }
}

// ----------------------------------------------------------------------
